//===-- HostProcessWindows.cpp ----------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "lldb/Host/FileSpec.h"
#include "lldb/Host/HostThread.h"
#include "lldb/Host/ThreadLauncher.h"
#include "lldb/Host/windows/windows.h"
#include "lldb/Host/windows/HostProcessWindows.h"

#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/ConvertUTF.h"

#include <Psapi.h>

using namespace lldb_private;

namespace
{
struct MonitorInfo
{
    HostProcess::MonitorCallback callback;
    void *baton;
    HANDLE process_handle;
};
}

HostProcessWindows::HostProcessWindows()
    : HostNativeProcessBase()
    , m_owns_handle(true)
{
}

HostProcessWindows::HostProcessWindows(lldb::process_t process)
    : HostNativeProcessBase(process)
    , m_owns_handle(true)
{
}

HostProcessWindows::~HostProcessWindows()
{
    Close();
}

void
HostProcessWindows::SetOwnsHandle(bool owns)
{
    m_owns_handle = owns;
}

Error HostProcessWindows::Terminate()
{
    Error error;
    if (m_process == nullptr)
        error.SetError(ERROR_INVALID_HANDLE, lldb::eErrorTypeWin32);

    if (!::TerminateProcess(m_process, 0))
        error.SetError(::GetLastError(), lldb::eErrorTypeWin32);

    return error;
}

Error HostProcessWindows::GetMainModule(FileSpec &file_spec) const
{
    Error error;
    if (m_process == nullptr)
        error.SetError(ERROR_INVALID_HANDLE, lldb::eErrorTypeWin32);

    std::vector<wchar_t> wpath(PATH_MAX);
    if (::GetProcessImageFileNameW(m_process, wpath.data(), wpath.size()))
    {
        std::string path;
        if (llvm::convertWideToUTF8(wpath.data(), path))
            file_spec.SetFile(path, false);
        else
            error.SetErrorString("Error converting path to UTF-8");
    }
    else
        error.SetError(::GetLastError(), lldb::eErrorTypeWin32);

    return error;
}

lldb::pid_t HostProcessWindows::GetProcessId() const
{
    return (m_process == LLDB_INVALID_PROCESS) ? -1 : ::GetProcessId(m_process);
}

bool HostProcessWindows::IsRunning() const
{
    if (m_process == nullptr)
        return false;

    DWORD code = 0;
    if (!::GetExitCodeProcess(m_process, &code))
        return false;

    return (code == STILL_ACTIVE);
}

HostThread
HostProcessWindows::StartMonitoring(HostProcess::MonitorCallback callback, void *callback_baton, bool monitor_signals)
{
    HostThread monitor_thread;
    MonitorInfo *info = new MonitorInfo;
    info->callback = callback;
    info->baton = callback_baton;

    // Since the life of this HostProcessWindows instance and the life of the process may be different, duplicate the handle so that
    // the monitor thread can have ownership over its own copy of the handle.
    HostThread result;
    if (::DuplicateHandle(GetCurrentProcess(), m_process, GetCurrentProcess(), &info->process_handle, 0, FALSE, DUPLICATE_SAME_ACCESS))
        result = ThreadLauncher::LaunchThread("ChildProcessMonitor", HostProcessWindows::MonitorThread, info, nullptr);
    return result;
}

lldb::thread_result_t
HostProcessWindows::MonitorThread(void *thread_arg)
{
    DWORD exit_code;

    MonitorInfo *info = static_cast<MonitorInfo *>(thread_arg);
    if (info)
    {
        ::WaitForSingleObject(info->process_handle, INFINITE);
        ::GetExitCodeProcess(info->process_handle, &exit_code);
        info->callback(info->baton, ::GetProcessId(info->process_handle), true, 0, exit_code);
        ::CloseHandle(info->process_handle);
        delete (info);
    }
    return 0;
}

void HostProcessWindows::Close()
{
    if (m_owns_handle && m_process != LLDB_INVALID_PROCESS)
        ::CloseHandle(m_process);
    m_process = nullptr;
}
